import { Calculator, Calculator_arg } from "./Calculator";
import { ValueObject } from "./ValueObject";

export class ValueObjectArray extends ValueObject {
    private _list: Array<ValueObject> = [];
    constructor(args?: string[] | null, childArgs?: string[], childNum?: number) {
        super(args);
        this.setProp("length", 0);
        this.on("length", (_: string, l: number) => {
            let n = l - 1;
            if (n < 0)
                return;
            try {
                Object.defineProperty(this, n, {
                    get() {
                        return this._list[n];
                    },
                    set(v) {
                        this.setChild(n, v);
                    }
                });
                Object.defineProperty(this, -l, {
                    get() {
                        return this._list[-l + this._list.length];
                    },
                    set(v) {
                        this.setChild(-l + this._list.length, v);
                    }
                });
            } catch { }
        }, this);
        if (childArgs) {
            let num = childNum || 1;
            for (let i = 0; i < num; i++) {
                this.push(new ValueObject(childArgs));
            }
        }
    }

    public sum(key: string) {
        let sum = 0;
        for (let i = 0; i < this._list.length; i++) {
            sum += this._list[i].get(key);
        }
        return sum;
    }

    public avg(key: string) {
        return this.sum(key) / this._list.length;
    }

    public max(key: string) {
        let max = this._list[0].get(key);
        for (let i = 1; i < this._list.length; i++) {
            if (this._list[i].get(key) > max) {
                max = this._list[i].get(key);
            }
        }
        return max;
    }

    public bindChild(key: string, fml: string) {
        let s = fml.split(":", 2);
        let func;
        let voKey = "children";
        if (s[0] == "sum") {
            func = this.sum;
        } else if (s[0] == "avg") {
            func = this.avg;
        } else if (s[0] == "max") {
            func = this.max;
        } else if (s[0] == "child") {
            func = this.chooseChild;
            voKey = "child";
        } else {
            return;
        }
        // let prop = isNaN(Number(s[1])) ? s[1] : Number(s[1]);
        this.setCalculator(key, {
            func: func,
            args: [{
                vo: voKey,
                prop: s[1],
                default: null
            }]
        });
        this.setProp(key);
    }

    public chooseChild(idx: number) {
        idx = Number(idx);
        if (idx < 0) {
            return this._list[this._list.length + idx];
        } else {
            return this._list[idx];
        }
    }

    public setCalculator(key: string, calculator: Calculator): void {
        this._calculators.set(key, calculator);
        for (let i = 0; i < calculator.args.length; i++) {
            let arg = calculator.args[i];
            this._bindings.push({
                dstProp: key,
                srcVoKey: arg.vo,
                srcProp: arg.prop
            });
            let vo;
            if (arg.vo == "children") {
                for (let i = 0; i < this._list.length; i++) {
                    vo = this._list[i];
                    vo.addConnection(arg.prop, this, key);
                }
            }
        }
        this.setDirty(key);
    }

    public push(vo: ValueObject) {
        this._list.push(vo);
        this._addChildConnection(vo, this._list.length - 1);
        this.length = this._list.length;
    }

    private _addChildConnection(child: ValueObject, idx: number) {
        for (let i = 0; i < this._bindings.length; i++) {
            let b = this._bindings[i];
            if (b.srcVoKey == "children") {
                child.addConnection(b.srcProp, this, b.dstProp);
                this.setDirty(b.dstProp);
            } else if (b.srcVoKey == "child" && this._checkIdx(b.srcProp, idx)) {
                this.setDirty(b.dstProp);
            }
        }
    }

    private _checkIdx(str: string, start: number, end?: number) {
        let i = Number(str);
        if (i < 0)
            i = i + this._list.length;

        end = end ? end : start;
        if (i >= start && i <= end)
            return true;
        return false;
    }

    public insert(vo: ValueObject, idx: number) {
        this._list.splice(idx, 0, vo);
        this._addChildConnection(vo, idx);
        this.length = this._list.length;

        for (let i = 0; i < this._bindings.length; i++) {
            let b = this._bindings[i];
            if (b.srcVoKey == "child" && this._checkIdx(b.srcProp, idx + 1, this._list.length - 1)) {
                this.setDirty(b.dstProp);
            }
        }
    }

    public setChild(idx: number, v: any) {
        let old = this._list[idx];
        if (old) {
            old.removeConnectionsByTarget(this);
        }
        this._list[idx] = v;
        this._addChildConnection(v, idx);
    }

    protected _parseArg(arg: Calculator_arg): any {
        if (arg.vo == "children" || arg.vo == "child") {
            return arg.prop;
        } else {
            return super._parseArg(arg);
        }
    }

    public addConnection(srcProp: string, dstVo: ValueObject, dstProp: string): void {
        ValueObject.prototype.addConnection.call(this, srcProp, dstVo, dstProp);
        let s = srcProp.split("(");
        if (s[1]) {
            this.bindChild(srcProp, srcProp.replace("(", ":").replace(")", ""));
        }
    }

    public addFilterConnection(srcProp: string, dstVo: ValueObjectArray,) {
        for (let i = 0; i < this._list.length; i++) {
            let child = this._list[i];
            child.addConnection(srcProp, dstVo, "total");
        }
    }

    // // 
    // public filter(ori:ValueObjectArray, fml:string) {
    //     ori.addConnection()
    // }
}